﻿using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Linq.Expressions;
using System;
using System.Reflection.Emit;

namespace ObjectDB.Library
{
	public class AssemblyGenerator
	{
		private AssemblyDefinition _asmDef;

		public AssemblyGenerator(string assemblyName)
		{
			_asmDef = AssemblyFactory.DefineAssembly("TestingDB", "MainModule", TargetRuntime.NET_4_0, AssemblyKind.Dll);
		}

		public Assembly GenerateAssembly()
		{
			byte[] rawAssembly = null;
			//AssemblyFactory.SaveAssembly(_asmDef, @"c:/testing.dll");
			AssemblyFactory.SaveAssembly(_asmDef, out rawAssembly);
			return Assembly.Load(rawAssembly);
		}

		public TypeGenerator DefineType(string typeName, bool isPublic = true, bool isClass = true)
		{
			var attributes = isPublic ? Mono.Cecil.TypeAttributes.Public : Mono.Cecil.TypeAttributes.NotPublic;
			attributes |= isClass ? Mono.Cecil.TypeAttributes.Class : attributes;

			var baseType = _asmDef.MainModule.Import(typeof(object));
			var typedef = new TypeDefinition(typeName, _asmDef.Name.Name, attributes, baseType);
			return new TypeGenerator(this, typedef);
		}

		public class TypeGenerator
		{
			private AssemblyGenerator _asmGen;
			private TypeDefinition _typeDef;

			internal TypeGenerator(AssemblyGenerator asmGen, TypeDefinition typeDef)
			{
				_asmGen = asmGen;
				_typeDef = typeDef;
			}

			public AssemblyGenerator GenerateType()
			{
				_asmGen._asmDef.MainModule.Types.Add(_typeDef);
				return _asmGen;
			}

			public TypeGenerator DefineConstructor()
			{
				var voidType = _asmGen._asmDef.MainModule.Import(typeof(void));

				//var methodRef = new MethodReference(".ctor", _asmGen._asmDef.MainModule.Import(typeof(SerializableAttribute)), voidType, true, true, MethodCallingConvention.Default);
				//_typeDef.CustomAttributes.Add(new CustomAttribute(methodRef));
				_typeDef.IsSerializable = true;

				var ctordef = new MethodDefinition(".ctor", Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.SpecialName | Mono.Cecil.MethodAttributes.RTSpecialName, voidType);
				//ctordef.Body.CilWorker.Append(ctordef.Body.CilWorker.Create(Cil.OpCodes.Ldarg_0));
				//ctordef.Body.CilWorker.Append(ctordef.Body.CilWorker.Create(Cil.OpCodes.Call, new MethodReference(".ctor", _asmGen._asmDef.MainModule.Import(typeof(object)), voidType, true, true, MethodCallingConvention.Default)));
				ctordef.Body.CilWorker.Append(ctordef.Body.CilWorker.Create(Mono.Cecil.Cil.OpCodes.Ret));
				_typeDef.Constructors.Add(ctordef);
				return this;
			}

			public TypeGenerator DefineField<TField>(string fieldName, bool isPublic = true)
			{
				var attributes = isPublic ? Mono.Cecil.FieldAttributes.Public : Mono.Cecil.FieldAttributes.Private;
				var fieldType = _asmGen._asmDef.MainModule.Import(typeof(TField));
				_typeDef.Fields.Add(new FieldDefinition(fieldName, fieldType, attributes));
				return this;
			}

			//public TypeGenerator DefineMethod(string methodName)
			//{
			//    var floatType = _asmGen._asmDef.MainModule.Import(typeof(double));
			//    MethodDefinition methodDef = new MethodDefinition(methodName, Mono.Cecil.MethodAttributes.Public, floatType);

			//    var left = Expression.Parameter(typeof(double));
			//    var right = Expression.Parameter(typeof(double));
			//    var lambda = Expression.Lambda<Func<double, double, double>>(Expression.Power(left, right), left, right);
			//    var methodBuilder = new DynamicMethod("testing", typeof(double), new Type[] { typeof(double), typeof(double) });
				
			//    var method = lambda.CompileToMethod(MethodBuilder);
			//    var methodBody = method.GetMethodBody();
			//    var bytes = methodBody.GetILAsByteArray();

			//    foreach (var b in bytes)
			//    {
			//        methodDef.
			//        var code = new Mono.Cecil.Cil.OpCode();
			//        code.GetType().GetField("m_value", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(code, b);
			//        var opCode = new Mono.Cecil.Cil.OpCode();
			//        opCode.GetType().GetField("m_code", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(opCode, code);
			//        methodDef.Body.CilWorker.Append(methodDef.Body.CilWorker.Create(opCode));
			//    }

			//    return this;
			//}
		}
	}
}